package com.lemurian.bankdata.bean;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.persistence.Transient;

import org.hibernate.Criteria;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.transform.Transformers;
import org.springframework.stereotype.Repository;

import com.lemurian.bankdata.bean.base.BaseDaoHibernate;
import com.lemurian.bankdata.dto.UserDTO;
import com.lemurian.bankdata.lite.enums.UserStatus;
import com.lemurian.bankdata.lite.logger.BankDataLogger;
import com.lemurian.bankdata.lite.struts.PartialList;
import com.lemurian.bankdata.lite.utils.BankDataCommons;
import com.lemurian.bankdata.lite.utils.Encryption;
import com.lemurian.entity.Category;
import com.lemurian.entity.Groups;
import com.lemurian.entity.Menu;
import com.lemurian.entity.Organization;
import com.lemurian.entity.User;

@Repository
public class UserDAO extends BaseDaoHibernate<User>{
	
	private BankDataLogger log = BankDataLogger.getLogger(UserDAO.class.getName());
	
	private int sessionValidTime = Integer.parseInt(BankDataCommons.getBankDataConfig().getString("session.valid.time"));
	
	@Override
	public User load(int id){
		User entity = null;
		try {
			Criteria criteria = getSession().createCriteria(User.class);
			
			criteria.setCacheable(Boolean.TRUE);
			
			criteria.createCriteria("userType", "userType", Criteria.LEFT_JOIN)
				.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
				.setCacheRegion(Category.class.getName());
			
			criteria.createCriteria("department", "department", Criteria.LEFT_JOIN)
				.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
				.setCacheRegion(Category.class.getName());
			
			criteria.createCriteria("groups", "groups", Criteria.LEFT_JOIN)
				.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
				.setCacheRegion(Groups.class.getName());
			
			criteria.createCriteria("organization", "organization", Criteria.LEFT_JOIN)
				.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
				.setCacheRegion(Organization.class.getName());
			
			criteria.add(Restrictions.eq("id", id));
			
			entity = (User) criteria.uniqueResult();
			
		} catch(Exception exception){
			getSession().getTransaction().rollback();
		}
		return entity;
	}
	
	@Override
	public PartialList<User> findByCriteria(int start, int count, Order order, Criterion... criterion) {
		PartialList<User> partialList = null;
		try {
			// total
			Criteria criteria = getSession().createCriteria(User.class).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
			
			criteria.setCacheable(true);
			
			for (Criterion c : criterion) {
				criteria.add(c);
			}
			criteria.setProjection(Projections.rowCount());
			int total = ((Integer) criteria.list().iterator().next()).intValue();
			
			// partial data
			criteria = getSession().createCriteria(User.class).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
			criteria.setCacheable(true);
			
			criteria.createCriteria("userType", "userType", Criteria.LEFT_JOIN).setResultTransformer(Criteria.ROOT_ENTITY);
			
			for (Criterion c : criterion) {
				criteria.add(c);
			}
			
			if (order!=null) criteria.addOrder(order);
			
			log.info(": : : : : START : "+start);
			log.info(": : : : : COUNT : "+count);
			
			criteria.setFirstResult(start);
			criteria.setMaxResults(count);
			
			partialList = new PartialList<User>(criteria.list(), total);
		} catch(Exception exception){
			getSession().getTransaction().rollback();
		}
		return partialList;
	}
	
	@Transient
	public PartialList<UserDTO> listUser(int start, int count, String ascDesc, String orderBy, String name, String email) {
		PartialList<UserDTO> list = null;
		
		try {
			// total
			Criteria criteria = initializeCriteria(start, count, ascDesc, orderBy, name, email);
			
			criteria.setProjection(Projections.rowCount());
			
			int total = ((Integer) criteria.list().iterator().next()).intValue();
			
			// partial data
			criteria = initializeCriteria(start, count, ascDesc, orderBy, name, email);
			
			criteria.setResultTransformer(Transformers.aliasToBean(UserDTO.class));
			
			log.info(": : : : : START : "+start);
			log.info(": : : : : COUNT : "+count);
			
			criteria.setFirstResult(start);
			criteria.setMaxResults(count);
			
			list = new PartialList<UserDTO>(criteria.list(), total);
			
		} catch (Throwable t) {
			t.printStackTrace();
		}
		
		return list;
	}
	
	public Criteria initializeCriteria (int start, int count, String ascDesc, String orderBy, String name, String email) {
		Criteria criteria = getSession().createCriteria(User.class);
		
		try {
			
			criteria.setCacheable(true);
			
			criteria.createCriteria("userType", "userType", Criteria.LEFT_JOIN).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
			
			criteria.setProjection(initializeProjection());
			
			if(name != null && name.length() > 0) {
				criteria.add(Restrictions.ilike("name", name));
			}
			
			if(email != null && email.length() > 0) {
				criteria.add(Restrictions.ilike("email", email));
			}
			
			if (ascDesc.equalsIgnoreCase("desc")) {
				criteria.addOrder(Order.desc(orderBy.length() > 0 ? orderBy : "id"));
			} else {
				criteria.addOrder(Order.asc(orderBy.length() > 0 ? orderBy : "id"));
			}
			
		} catch (Throwable t) {
			t.printStackTrace();
		}
		
		return criteria;
	}
	
	public ProjectionList initializeProjection() {
		ProjectionList projection = Projections.projectionList();
		
		try {
			
			projection.add(Projections.property("id"));
			projection.add(Projections.property("name").as("name"));
			projection.add(Projections.property("loginId").as("loginId"));
			projection.add(Projections.property("email").as("email"));
			projection.add(Projections.property("mobilePhone").as("phone"));
			projection.add(Projections.property("city").as("city"));
			projection.add(Projections.property("userType.name").as("userType"));
			projection.add(Projections.property("groups").as("groups"));
			
			projection.add(Projections.groupProperty("id"));
			projection.add(Projections.groupProperty("name"));
			projection.add(Projections.groupProperty("loginId"));
			projection.add(Projections.groupProperty("email"));
			projection.add(Projections.groupProperty("mobilePhone"));
			projection.add(Projections.groupProperty("city"));
			projection.add(Projections.groupProperty("loginId"));
			projection.add(Projections.groupProperty("userType.id"));
			projection.add(Projections.groupProperty("userType.name"));
			
		} catch (Throwable t) {
            t.printStackTrace();
        }
		
		return projection;
	}
	
	public boolean validateUserSession(User user, String sessionId) {
        try {
        	user = load(user.getId());
            
            log.info(": : : : : session id   [" + sessionId + "]");
            log.info(": : : : : valid time   [" + user.getSessionValidTime() + "]");
            log.info(": : : : : current time [" + String.valueOf(new Date()) + "]");
            
            if (user.getSessionId() != null && user.getSessionValidTime() != null && user.getSessionId().equals(sessionId) && (user.getSessionValidTime()).after(new Date())) {
            	
                Calendar cal = Calendar.getInstance();
                cal.setTime(new Date());
                cal.add(Calendar.MINUTE, sessionValidTime); // set valid time 30 minute
                user.setSessionValidTime(cal.getTime());
                
                save(user);
                
                return true;
            }
            
        } catch (Throwable th) {
            th.printStackTrace();
        }
        
        return false;
    }
	
	public User authenticate(String loginId, String password) {
		User user = null;

		try {
			
			Criteria criteria = getSession().createCriteria(User.class).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
			
			criteria.createCriteria("department", "department", Criteria.LEFT_JOIN)
				.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
				.setCacheRegion(Category.class.getName());
			
			criteria.add(Restrictions.or(Restrictions.eq("loginId", loginId), Restrictions.eq("email", loginId)));
			
			user = (User) criteria.uniqueResult();
			
			password = Encryption.SHA1(password);
			
			if(user != null && !user.getPassword().equalsIgnoreCase(password)) {
                return null;
            }
			
		} catch (Throwable th) {
            th.printStackTrace();
        }
		
		return user;
	}
	
	public boolean saveSessionId(User user, String sessionId) {
		try {
			
            //================= Save session id & valid time ================
            Calendar cal = Calendar.getInstance();
            cal.setTime(new Date());
            cal.add(Calendar.MINUTE, sessionValidTime); // set valid time 30 minute
            user.setSessionId(sessionId);
            user.setSessionValidTime(cal.getTime());
            
            save(user);
            //================= End of save session id ======================
            
        } catch (Throwable th) {
            th.printStackTrace();
        }
        
        return false;
	}
	
	public void failCount(String loginId) {
		User user = null;
        try {
        	Criteria criteria = getSession().createCriteria(User.class).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

            criteria.setCacheable(Boolean.TRUE);

            criteria.add(Restrictions.or(Restrictions.eq("loginId", loginId), Restrictions.eq("email", loginId)));

            user = (User) criteria.uniqueResult();

            if (user != null) {
            	user.setFailCount(user.getFailCount() + 1);

                if (user.getFailCount() >= 3) {
                    user.setStatus(UserStatus.LOCKED.code());
                }

                save(user);
            }

        } catch (Throwable t) {
            t.printStackTrace();
            getSession().getTransaction().rollback();
        }
	}
	
	public <T extends User> List<Menu> getListMenu(T user) {
		List<Menu> menus = null;

		try {
			Criteria criteria = getSession().createCriteria(User.class).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
			
			criteria.createCriteria("groups", "groups", Criteria.INNER_JOIN)
					.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
					.setCacheRegion(Groups.class.getName());
			
			criteria.createCriteria("groups.menus", "menus", Criteria.INNER_JOIN)
					.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
					.setCacheRegion(Menu.class.getName());
			
			criteria.add(Restrictions.or(Restrictions.eq("menus.showParent", Boolean.TRUE), Restrictions.eq("menus.show", Boolean.TRUE)));
			
			criteria.createCriteria("menus.childs", "childs", Criteria.LEFT_JOIN)
					.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
					.setCacheRegion(Menu.class.getName());
			
			criteria.add(Restrictions.eq("id", user.getId()));
			
			user = (T) criteria.uniqueResult();
			
			menus = new ArrayList<Menu>();
			for (Groups g : user.getGroups()) {

				for(Menu m : g.getMenus()) {
					
					if(m != null) {
						menus.add(m);
					}
				}
				
			}
			
			user.setGroups(null);
			
		} catch (Throwable t) {
            t.printStackTrace();
        }
		
		return menus;
	}
	
	public User resetFailCount(User user) {
        try {
        	
        	user.setFailCount(0);

            user.setStatus(UserStatus.ACTIVE.code());

            save(user);
        	

        } catch (Throwable t) {

        	Criteria criteria = getSession().createCriteria(User.class).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

            criteria.setCacheable(Boolean.TRUE);

            criteria.add(Restrictions.or(Restrictions.eq("loginId", user.getLoginId()), Restrictions.eq("email", user.getEmail())));

            user = (User) criteria.uniqueResult();
            
            user.setFailCount(0);

            user.setStatus(UserStatus.ACTIVE.code());

            save(user);
        }
        
        return user;
	}
	
	public List<User> getUserByUserType(int userType) {
		List<User> users = null;
		
		try {
			Criteria criteria = getSession().createCriteria(User.class);
			
			criteria.createCriteria("userType", "userType", Criteria.INNER_JOIN)
				.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
				.setCacheRegion(Category.class.getName());
			
			criteria.add(Restrictions.eq("userType.id", userType));
			
			users = criteria.list();
			
		} catch (Throwable t) {
			t.printStackTrace();
		}
		
		return users;
	}
}